﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Controls;

using XmlVisualizer;

namespace XmlVisualizer.DiagramDesigner {
	/// <summary>
	/// Connector adorner class.
	/// </summary>
    public class ConnectorAdorner : Adorner {
		#region Fields
		private PathGeometry pathGeometry;
		private DesignerCanvas canvas;
		private Connector sourceConnector;
		private Pen drawingPen;
		#endregion		

		#region Properties
		private DesignerItem hitDesignerItem;
		/// <summary>
		/// Gets or sets the hit DesignerItem.
		/// </summary>
		private DesignerItem HitDesignerItem {
			get { return hitDesignerItem; }
			set {
				if (hitDesignerItem != value) {
					if (hitDesignerItem != null)
						hitDesignerItem.IsDragConnectionOver = false;

					hitDesignerItem = value;

					if (hitDesignerItem != null)
						hitDesignerItem.IsDragConnectionOver = true;
				}
			}
		}

		private Connector hitConnector;
		/// <summary>
		/// Gets or sets the hit Connector.
		/// </summary>
		private Connector HitConnector {
			get { return hitConnector; }
			set {
				if (hitConnector != value) {
					hitConnector = value;
				}
			}
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Initializes a new instance of the ConnectorAdorner class. Sets canvas and source Connector.
		/// </summary>
		/// <param name="canvas">DesignerCanvas on which the items are placed.</param>
		/// <param name="sourceConnector">Source Connector.</param>
		/// <exception cref="System.ArgumentNullException">Canvas or sourceConnector is null.</exception>
		public ConnectorAdorner(DesignerCanvas canvas, Connector sourceConnector)
			: base(canvas) {
			if (canvas == null)
				throw new ArgumentNullException("canvas");
			if (sourceConnector == null)
				throw new ArgumentNullException("sourceConnector");

			this.canvas = canvas;
			this.sourceConnector = sourceConnector;
			drawingPen = new Pen(Brushes.LightSlateGray, 1);
			drawingPen.LineJoin = PenLineJoin.Round;
			this.Cursor = Cursors.Cross;
		}
		#endregion		

		#region Event handlers
		/// <summary>
		/// Invoked on MouseUp event.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected override void OnMouseUp(MouseButtonEventArgs e) {
			if (HitConnector != null) {
				Connector sourceConnector = this.sourceConnector;
				Connector sinkConnector = this.HitConnector;
				
				bool addConnection = true;
				
				ConnectResult res = sourceConnector.ParentDesignerItem.TryConnect(sourceConnector, sinkConnector);
				
				switch (res) {
					case ConnectResult.ChildHasParent:
						addConnection = false;
						MessageBoxes.ShowError("MessageBox.ChildHasParent.Message");
						break;
					case ConnectResult.ParentNodeNotElement:
						addConnection = false;
						MessageBoxes.ShowError("MessageBox.ParentNodeNotElement.Message");
						break;
					case ConnectResult.None:
						addConnection = false;
						break;
				}

				if (addConnection) {
					Connection newConnection = new Connection(sourceConnector, sinkConnector);
					Canvas.SetZIndex(newConnection, canvas.Children.Count);
					this.canvas.Children.Add(newConnection);
				}
			}
			if (HitDesignerItem != null) {
				this.HitDesignerItem.IsDragConnectionOver = false;
			}

			if (this.IsMouseCaptured)
				this.ReleaseMouseCapture();

			AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.canvas);
			if (adornerLayer != null) {
				adornerLayer.Remove(this);
			}
		}

		/// <summary>
		/// Invoked on MouseMove event.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected override void OnMouseMove(MouseEventArgs e) {
			if (e.LeftButton == MouseButtonState.Pressed) {
				if (!this.IsMouseCaptured)
					this.CaptureMouse();
				HitTesting(e.GetPosition(this));
				this.pathGeometry = GetPathGeometry(e.GetPosition(this));
				this.InvalidateVisual();
			} else {
				if (this.IsMouseCaptured)
					this.ReleaseMouseCapture();
			}
		}

		/// <summary>
		/// Invoked on Render event.
		/// </summary>
		/// <param name="dc">DrawingContext to draw with.</param>
		protected override void OnRender(DrawingContext dc) {
			base.OnRender(dc);
			dc.DrawGeometry(null, drawingPen, this.pathGeometry);

			// without a background the OnMouseMove event would not be fired
			// Alternative: implement a Canvas as a child of this adorner, like
			// the ConnectionAdorner does.
			dc.DrawRectangle(Brushes.Transparent, null, new Rect(RenderSize));
		}
		#endregion

		#region Methods
		/// <summary>
		/// Gets the PathGeometry for the connection line.
		/// </summary>
		/// <param name="position">Position of the other connection point.</param>
		/// <returns>Filled PathGeometry.</returns>
		private PathGeometry GetPathGeometry(Point position) {
			PathGeometry geometry = new PathGeometry();

			ConnectorOrientation targetOrientation;
			if (HitConnector != null)
				targetOrientation = HitConnector.Orientation;
			else
				targetOrientation = ConnectorOrientation.None;

			List<Point> pathPoints = PathFinder.GetConnectionLine(sourceConnector.GetInfo(), position, targetOrientation);

			if (pathPoints.Count > 0) {
				PathFigure figure = new PathFigure();
				figure.StartPoint = pathPoints[0];
				pathPoints.Remove(pathPoints[0]);
				figure.Segments.Add(new PolyLineSegment(pathPoints, true));
				geometry.Figures.Add(figure);
			}

			return geometry;
		}

		/// <summary>
		/// Hit testing with the point.
		/// </summary>
		/// <param name="hitPoint">Point to hit test with.</param>
		private void HitTesting(Point hitPoint) {
			bool hitConnectorFlag = false;

			DependencyObject hitObject = canvas.InputHitTest(hitPoint) as DependencyObject;
			while (hitObject != null &&
				   hitObject != sourceConnector.ParentDesignerItem &&
				   hitObject.GetType() != typeof(DesignerCanvas)) {
				if (hitObject is Connector) {
					HitConnector = hitObject as Connector;
					hitConnectorFlag = true;
				}

				if (hitObject is DesignerItem) {
					HitDesignerItem = hitObject as DesignerItem;
					if (!hitConnectorFlag)
						HitConnector = null;
					return;
				}
				hitObject = VisualTreeHelper.GetParent(hitObject);
			}

			HitConnector = null;
			HitDesignerItem = null;
		}
		#endregion        
    }
}